home *** CD-ROM | disk | FTP | other *** search
/ AM/FM: Amiga Musicians' Freeware Magazine 1 / AM-FM 1.adf / text / TechCorner.pp / TechCorner
Text File  |  1991-10-03  |  25KB  |  609 lines

  1.         MIDI and the serial port
  2.         ~~~~~~~~~~~~~~~~~~~~~~~~
  3.         by Teijo Kinnunen (02-Jul-1991)
  4.  
  5. Welcome to the first TechCorner column of AM/FM!! This column is
  6. directed to programmers, and to anybody who is interested to know how
  7. the tricks/things of the music programs actually work.
  8.  
  9. What I'm going to give you in this and the future issues of AM/FM
  10. is tutorials about programming sound and music on the Amiga, with
  11. example programs and routines you can use in your own programs.
  12.  
  13. First (before you get bored...), I would like to say that all feedback
  14. about this column is welcome! I'd like hear from you which topics you
  15. would like me to cover in future issues of AM/FM.
  16.  
  17. My address is:        Teijo Kinnunen
  18. ~~~~~~~~~~~~~~        Oksantie 19
  19.             SF-86300  OULAINEN
  20.             FINLAND
  21.  
  22. If you want me to reply, please send a self-addressed envelope, and
  23. a small sum of money or international reply coupons to cover the postage.
  24. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  25. Now let's get started...as the headline says, I'm going to tell how
  26. to make serial routines for MIDI usage (these routines can, of course,
  27. be used for other purposes too).
  28.  
  29. I will use the MIDI routines I've ripped :^) from MED as a basis of the
  30. example routines, because they're well-tested and quite fast, but simple.
  31. I will explain as clearly as possible how these routines work.
  32. The routines are written in assembly language for maximum speed.
  33. Please feel free to use the routines in your own programs!
  34.  
  35. Actually there are two approaches of using the serial port. The first
  36. method is using the "serial.device" routines. The second is to use the
  37. hardware directly. I will cover the latter method, because it's somewhat
  38. easier, and because it can be used from an interrupt (which is quite
  39. important in music programs, for smooth'n'steady output). Note that it
  40. IS possible to use the hardware directly in a manner which works well
  41. in a multitasking system, and that's exactly how we're going to do it.
  42.  
  43. The very first thing we MUST do is to allocate the serial port for our
  44. exclusive use. This is VERY important!! We WON'T use methods of some
  45. programmers who don't know that the Amiga is a multitasking computer.
  46.  
  47. The serial port is owned by "misc.resource", and if we ask kindly,
  48. it may lend the port to us. Misc.resource contains two routines that we
  49. will use: AllocMiscResource() and FreeMiscResource(). These routines
  50. are used for allocating/freeing some of the miscellaneous hardware
  51. resources (such as the serial port).
  52.  
  53. (Resources are a kind of "libraries" that handle the sharing of the
  54. low-level hardware resources in the multitasking environment of the
  55. Amiga. For more information, read the Rom Kernel Manuals.)
  56.  
  57. So, our first routine is:
  58. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  59.  
  60. GetSer:        move.l    a6,-(sp) ;remember: only a0/a1/d0/d1 may be trashed!
  61.         movea.l    4,a6        ;ExecBase
  62. ;First we use the exec routine OpenResource() to open the misc.resource.
  63. ;I usually enter directly the offsets of the ROM routines, you can use
  64. ;the LVO's etc. if you want.
  65.         moveq    #0,d0    ;any version of the resource is OK
  66.         lea    miscresname(pc),a1
  67.         jsr    -$1f2(a6)    ;OpenResource()
  68. ;Then we test if "misc.resource" were opened. It's in ROM, so this should
  69. ;never fail, but we'll check it anyway.
  70.         tst.l    d0
  71.         beq.s    GS_error
  72. ;The pointer to MiscResBase must be saved...it'll be needed later.
  73.         move.l    d0,miscresbase
  74. ;It must be moved to a6 for the AllocMiscResource() call.
  75.         move.l    d0,a6
  76. ;The first argument of AllocMiscResource(), is the number of the resource
  77. ;we want to allocate. In this case it's MR_SERIALPORT, which is defined
  78. ;in "resources/misc.i". We don't load the include file, because we know
  79. ;it's 0.
  80.         moveq    #0,d0
  81. ;AllocMiscResource() requires the name of the program that tries to
  82. ;allocate the resource (never set it to zero!!). If any other program
  83. ;tries to allocate the serial port, it will get the name of our program
  84. ;(so it can e.g. print an error message "Serial port allocated by MyProggie").
  85.         lea    myname(pc),a1
  86. ;The offset of AllocMiscResource() is -6 (defined in resources/misc.i).
  87.         jsr    -$6(a6)    ;AllocMiscResource()
  88. ;If d0 contains zero after the call, the serial port was successfully
  89. ;allocated and is now ours. If not, we should exit with error.
  90.         tst.l    d0
  91.         bne.s    GS_error
  92. ;It's always good to keep track of the resources we've allocated.
  93. ;We'll now set a single byte labelled 'serportalloc', so we remember
  94. ;that we own that port.
  95.         st    serportalloc
  96. ;Now it's done. We will return 0 in d0, because no errors happened.
  97.         moveq    #0,d0
  98. GS_exit        move.l    (sp)+,a6    ;restore a6
  99.         rts
  100. ;If an error happened, we'll just return -1.
  101. GS_error    moveq    #-1,d0
  102.         bra.s    GS_exit
  103.  
  104. miscresname    dc.b    'misc.resource',0
  105. serportalloc    dc.b    0
  106. myname        dc.b    'MyProggie',0 ;put the name of your program here
  107.         even    ;longwords must be aligned (ever heard of that?)
  108. miscresbase    dc.l    0
  109.  
  110. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  111. Now the bad news: This routine may fail even if no program is actually
  112. using the serial port. But don't panic yet...
  113.  
  114. The reason for that is the "serial.device". If you've been using a program
  115. that does serial IO using the serial.device, the serial port is still
  116. owned by the serial.device even if the program was no longer running. The
  117. serial.device keeps itself the port as long as it's in memory, so we must
  118. try to "flush" the device from the memory. Before removing itself, the
  119. serial.device frees all its resources, including the serial port.
  120.  
  121. So, here's another routine that tries to allocate the serial port using
  122. the above routine, and if that fails, it will attempt to flush the
  123. serial.device and try again.
  124. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  125. ;We will precede the function name with '_', so it can be called from
  126. ;routines made in C (XDEF will be required then)
  127. _GetSerial    move.l    a6,-(sp)    ;a6 will be trashed
  128.         bsr.s    GetSer        ;Try without flushing serial.device
  129. ;If d0 == 0 we succeeded, so there's no need to flush the device.
  130.         tst.l    d0
  131.         beq.s    GSerial_exit
  132. ;In the case it didn't succeed, we'll have to flush serial.device.
  133. ;First it's best to disable multitasking, because we're accessing
  134. ;the device list of ExecBase.
  135.         movea.l    4,a6
  136.         jsr    -$84(a6)    ;Forbid()
  137. ;Then we'll have to find the base structure of serial.device. The devices
  138. ;are held in a device list (located at offset $15E of ExecBase). We use
  139. ;the routine FindName() to find an entry called serial.device.
  140.         lea    $15e(a6),a0    ;a0 = pointer of the list
  141.         lea    serdevname(pc),a1    ;a1 = name ("serial.device")
  142.         jsr    -$114(a6)    ;FindName()
  143. ;The pointer is in d0. If it's zero, serial.device was not found.
  144. ;In that case we'll skip the RemDevice()-part.
  145.         tst.l    d0
  146.         beq.s    GSerial_nosd
  147. ;We found the serial.device. Now we just call RemDevice() which does the
  148. ;dirty work. If serial.device is currently in use, this does nothing
  149. ;(actually, it may set the delayed expunge flag, but it doesn't matter....)
  150.         move.l    d0,a1
  151.         jsr    -$1b6(a6)    ;RemDevice()
  152. ;Enable multitasking again..
  153. GSerial_nosd    jsr    -$8a(a6)    ;Permit()
  154. ;Actually, RemDevice returns an error code if it fails, but we just
  155. ;try to call the GetSer routine again.
  156.         bsr.s    GetSer
  157. ;The return code from GetSer in d0 is also our return code.
  158. GSerial_exit    move.l    (sp)+,a6
  159.         rts
  160.  
  161. serdevname    dc.b    'serial.device',0
  162. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  163.  
  164. So, now we have the serial port. Then we have to talk a bit about the
  165. mechanism of the serial output routine. Here's how it works:
  166. We have one routine, called _AddMIDIData, which handles all output and
  167. does some optimization (the running status byte). This routine puts the
  168. data into a circular output buffer (128 bytes). Then there's a serial
  169. interrupt that runs asynchronously, fetches the bytes from the output
  170. buffer and pushes them to SERDAT register. The serial interrupt occurs
  171. automatically when the hardware has handled the most recent byte, and
  172. is ready to accept new data.
  173.  
  174. First, we must do some initialization: The interrupt must be installed,
  175. and the serial output speed must be set.
  176.  
  177. Now we calculate the output speed: The serial (as well as the audio) hardware
  178. measures the time as 'periods'. We also know that the MIDI speed is about
  179. 31200 bps (bits per second). So, time required to output one bit is 1/31200
  180. seconds (0,000032051 s). Then we look at the page 240 of the Hardware
  181. Reference Manual, and read: "If you consider the contents of these bits to
  182. be the number N, then N+1 color clocks (each 279.4 ns) occur between samples
  183. of the state of the input pin......" So, if each 'period' is 279.4 ns
  184. (0,0000002794 s), N+1 will be 0,000032051s / 0,0000002794 s, which is about
  185. 114,7136722, rounded 115. Therefore, N will be 115 - 1 = 114. This actually
  186. gives us baud rate of about 31396 bps, which is accurate enough.
  187. Note that the above calculation is for U.S. machines. The clock speed is
  188. slightly different on NTSC and PAL machines. The period value of 114 works
  189. fine with both. The actual value for PAL would be 113.
  190.  
  191. Then we must initialize the TBE (Transmit Buffer Empty) interrupt.
  192. This is naturally done using SetIntVector() instead of touching the
  193. zero page autovectors.
  194. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  195. _InitSerial    move.l    a6,-(sp)
  196. ;We check if the serial port was actually allocated. The caller of
  197. ;this routine *should* take care of that!
  198. ;(note that we use move.b instead of tst.b, because we can then use
  199. ; the pc-relative addressing, saving 2 bytes of memory)
  200.         move.b    serportalloc(pc),d0
  201.         beq.s    InitSer_exit
  202. ;Now, set the baud rate (as calculated above).
  203.         move.w    #114,$dff032    ;$dff032 = SERPER
  204. ;Install the TBE handler
  205.         moveq    #0,d0    ;INTB_TBE (hardware/intbits.i)
  206.         lea    tbeinterrupt(pc),a1    ;see definition below
  207.         movea.l    4,a6
  208.         jsr    -$a2(a6)    ;SetIntVector()
  209.         move.l    d0,prevtbe    ;store the addr. of former interrupt
  210. ;Now we're ready to enable that interrupt. We'll set the TBE
  211. ;bit of INTENA ($dff09a). (The interrupt won't occur yet, however.)
  212.         move.w    #$8001,$dff09a
  213. InitSer_exit    move.l    (sp)+,a6
  214.         rts
  215.  
  216. prevtbe        dc.l    0
  217. ;This is an Interrupt structure (see exec/interrupts.i).
  218. ;The IS_DATA field is set to point to the output buffer pointer (see below),
  219. ;so the address will be loaded into a1 beforehand.
  220. ;SerIntHandler is the pointer to the actual interrupt code (see below).
  221. tbeinterrupt    dc.w    0,0,0,0,0
  222.         dc.l    tbename,buffptr,SerIntHandler
  223. ;We should give our interrupt a name.
  224. tbename        dc.b    'MyProggie serial interrupt',0
  225. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  226.  
  227. Now begins the critical part...below is a listing of the serial interrupt
  228. handler routine. There are numerous optimizations to make it faster, for
  229. example, the order of the fields in the data section must not be changed
  230. (because address register relative data addressing is used).
  231.  
  232. Then I refresh your memory by telling which registers initially set
  233. by the Exec interrupt code we are using:
  234.     a0 = pointer to the custom chips ($dff000)
  235.     a1 = IS_DATA of the Interrupt structure, which was set to point to
  236.          the buffptr-field above (note: a1 = ADDRESS of buffptr, not
  237.          the contents of it)
  238.     a6 = ExecBase
  239. In addition, registers a5, d0 and d1 may (and will) be trashed.
  240.  
  241. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  242. SerIntHandler
  243. ;We disable interrupts, so that a music routine running on higher level
  244. ;interrupts can't change anything while we're working.
  245.         move.w    #$4000,$9a(a0)    ;Master bit of INTENA
  246. ;Actually, it's probably not absolutely necessary to increment the
  247. ;IDNestCnt of ExecBase. This interrupt wouldn't have occurred if the
  248. ;interrupts were disabled. But this is a example of "clean" code, so
  249. ;I left it here.
  250.         addq.b    #1,$126(a6)    ;increment ExecBase->IDNestCnt
  251. ;First we clear the TBE bit in INTREQ register (that actually caused
  252. ;this interrupt). Otherwise the interrupt would be immediately
  253. ;retriggered.
  254.         move.w    #1,$9c(a0)    ;$dff09c = INTREQ
  255. ;Load into d0 the number of bytes that must be sent (we can send only
  256. ;one at a time, however).
  257.         move.b    bytesinbuff(pc),d0
  258. ;If it's zero, there's nothing to send in the buffer.
  259.         beq.s    SerInt_bufempty
  260. ;Now the fun begins...First we get the READ pointer of the buffer
  261. ;(buffptr is the WRITE pointer). A1 points to buffptr, so readbuffptr
  262. ;is at 4(a1).
  263.         movea.l    4(a1),a5
  264. ;Then we initialize the data word that will be put into SERDAT register.
  265. ;There must be one stop bit.
  266.         move.w    #$100,d1
  267. ;Now the actual data byte must be read (it'll be placed at the lowest byte
  268. ;of d1), the read pointer will be incremented, too.
  269.         move.b    (a5)+,d1
  270. ;Then, we'll just push the word into SERDAT register, and the hardware
  271. ;takes care of the rest.
  272.         move.w    d1,$30(a0)    ;SERDAT = $dff030
  273. ;The byte is now sent. Then we must check and update the pointers.
  274. ;We compare the read pointer to a1 (address of buffptr). If they're equal,
  275. ;the end of the buffer has been reached, and the pointer must be reset.
  276.         cmpa.l    a1,a5
  277.         bne.s    SerInt_resrp    ;no need to reset
  278.         lea    sendbuffer(pc),a5    ;start address of the buffer
  279. SerInt_resrp
  280. ;d0 contains the number of bytes in the buffer. It must be decremented.
  281.         subq.b    #1,d0
  282.         move.b    d0,8(a1)    ;save it, 8(a1) = bytesinbuff
  283. ;Finally we save the buffer read pointer...
  284.         move.l    a5,4(a1)
  285. ;enable interrupts...
  286. SerInt_exit    subq.b    #1,$126(a6)
  287.         bge.s    SerInt_X
  288.         move.w    #$c000,$9a(a0)
  289. ;and exit.
  290. SerInt_X    rts
  291. ;Buffer is empty, so we set a flag. AddMIDIData() will then set
  292. ;TBE flag of INTREQ, so that the interrupt will be invoked again.
  293. SerInt_bufempty    st    9(a1)
  294.         bra.s    SerInt_exit
  295.  
  296. sendbuffer    ds.b    128
  297. buffptr        dc.l    sendbuffer    ;buffer WRITE pointer
  298. readbuffptr    dc.l    sendbuffer    ;buffer READ pointer
  299. bytesinbuff    dc.b    0
  300. ;Initially the buffer's empty, so we set this flag.
  301. bufferempty    dc.b    -1
  302. ;Storage for the latest status byte (rsb-optimization).
  303. lastcmdbyte    dc.b    0
  304. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305.  
  306. At this moment you should be ready to face the most complex routine:
  307. _AddMIDIData.
  308. This routine has two arguments:
  309. a0 = pointer to MIDI data
  310. d0 = length of the data
  311. It copies the MIDI data to the output buffer, and does the running status
  312. optimization (that allows you to leave out subsequent status bytes if
  313. they are the same).
  314. Note that this routine can't handle long data transfers, because
  315. the buffer is only 128 bytes long! To handle e.g. long SysEx messages you
  316. could:
  317. 1) increase the buffer size (this may also require some changes from
  318.    .b to .w etc..)
  319. 2) write an interrupt routine of your own, that can handle long,
  320.    continuous buffers.
  321. If - for some reason - your program sends out data faster than these
  322. routines can handle, the buffer may get filled and the data starts to
  323. overlap. No crash or guru will occur, but trashed data may be sent out.
  324.  
  325. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  326. ;Registers used:
  327. ;a0 = source pointer
  328. ;a1 = destination pointer (output buffer)
  329. ;a2 = pointer to buffptr (for address register relative data addressing)
  330. ;a6 = ExecBase
  331. ;d0 = number of bytes left
  332. ;d1 = data storage
  333.  
  334. _AddMIDIData
  335. ;Do the serportalloc-check...
  336.         move.b    serportalloc(pc),d1
  337.         beq.s    AMD_rts
  338.         movem.l    a2/a6,-(sp)    ;these will be trashed
  339.         movea.l    4,a6        ;get ExecBase...
  340. ;Disable(), so that nobody (the TBE interrupt) can disturb us...
  341.         move.w    #$4000,$dff09a
  342.         addq.b    #1,$126(a6)
  343. ;Load ptr to buffer pointer into a2.
  344.         lea    buffptr(pc),a2
  345. ;First test if the buffer is empty.
  346.         tst.b    9(a2)    ;uses the data struct defined above
  347.         beq.s    AMD_noTBEreq
  348. ;We clear the flag, the buffer will not be empty after a while.
  349.         clr.b    9(a1)
  350. ;It was empty, so we request the TBE to happen (because interrupts
  351. ;are disabled, the interrupt won't occur immediately).
  352.         move.w    #$8001,$dff09c
  353. ;Get the buffer write pointer
  354. AMD_noTBEreq    movea.l    (a2),a1
  355. ;Here begins the loop which fetches every byte and pushes it into the
  356. ;output buffer.
  357. AMD_dataloop    move.b    (a0)+,d1    ;get a byte
  358. ;We check if it's a status byte (for handling the optimization).
  359. ;It is status byte if it's >= $80.
  360.         bpl.s    AMD_nostatus
  361. ;It probably was a status byte. It may, however, be a realtime
  362. ;message (>= $F0), so we do another check.
  363.         cmp.b    #$ef,d1
  364.         bhi.s    AMD_nostatus
  365. ;Now we are sure that it's a status byte. Just check if it's the
  366. ;same as the previous status byte that was sent.
  367.         cmp.b    lastcmdbyte(pc),d1
  368. ;If it was the same, we skip the part that inserts the byte into
  369. ;the output buffer.
  370.         beq.s    AMD_noinsbyte
  371. ;In the case it wasn't, we save the status byte.
  372.         move.b    d1,10(a2)    ;10(a2) = lastcmdbyte
  373. ;Then we push the byte into the buffer...
  374. AMD_nostatus    move.b    d1,(a1)+
  375. ;Add the byte counter
  376.         addq.b    #1,8(a2)    ;8(a2) = bytesinbuff
  377. AMD_noinsbyte    cmpa.l    a2,a1        ;end of buffer reached?
  378.         bne.s    AMD_noptrreset
  379. ;Reset the buffer write pointer...
  380.         lea    sendbuffer(pc),a1
  381. ;Finally, check if there are any bytes still left, and if yes,
  382. ;jump to the start of the loop.
  383. AMD_noptrreset    subq.b    #1,d0
  384.         bne.s    AMD_dataloop
  385. ;Save the write buffer pointer.
  386.         move.l    a1,(a2)
  387. ;Enable interrupts, and exit.
  388.         subq.b    #1,$126(a6)
  389.         bge.s    AMD_x
  390.         move.w    #$c000,$dff09a
  391. AMD_x        movem.l    (sp)+,a2/a6
  392. AMD_rts        rts
  393.  
  394. ;data structure defined above
  395. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  396. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  397. So, now we have routines for putting out MIDI data. But what about INPUT??
  398. The input is definitely more trickier than output, as the routine should be
  399. able to interpret MIDI data, ignore things it's not interested in, handle
  400. realtime messages, SysEx, etc.etc... Therefore, it's impossible to make an
  401. all-purpose input routine.
  402.  
  403. However, as an example, I'll present you a simplified version of the input
  404. handler of MED. This routine recognizes only Note On and Note Off messages
  405. and ignores everything else. It has a three-byte buffer where it places the
  406. incoming notes. The main task is signalled when a note is read.
  407. The running status byte is recognized.
  408.  
  409. The interrupt to use is the RBF (Receive Buffer Full) interrupt, which runs
  410. as a level 6 (= high level) interrupt. However, this presents us another
  411. problem: If the play routine uses a level 6 interrupt (which is the case
  412. when CIAB timers are used), bytes may be lost because of the time needed
  413. to handle the interrupt (If the RBF occurs after starting executing the
  414. play routine, the interrupt will be handled AFTER exiting the play routine,
  415. because interrupts running on the same priority won't overlap. If another
  416. byte comes in BEFORE handling that interrupt, the previous byte will be
  417. lost). In MED (V3.11) I've get round that by using a copper interrupt which
  418. is called by the CIAB interrupt.
  419. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  420. ;TO BE ADDED TO _InitSerial
  421.         ...
  422.         moveq    #11,d0        ;RBF
  423.         lea    rbfinterrupt(pc),a1
  424.         jsr    -$a2(a6)    ;SetIntVector()
  425.         move.l    d0,prevrbf
  426.         ...
  427. ;IN ADDITION:    move.w    #$8001,$dff09a SHOULD BE REPLACED WITH
  428.         move.w    #$8801,$dff09a
  429. ;to activate both TBE and RBF
  430.  
  431.  
  432. prevrbf        dc.l    0
  433. rbfinterrupt    dc.w    0,0,0,0,0
  434.         dc.l    rbfname,recmidi,RBFIntHandler
  435.  
  436. ;Three external references are required (the main program should
  437. ;define these)
  438.         xref    _maintsk    ;the task to be signalled
  439.         xref    _sigmask    ;the signal mask
  440.         xref    _inputbuff    ;a 3-byte input note buffer
  441.  
  442. RBFIntHandler
  443. ;Immediately read the byte from SERDATR-register.
  444.         move.w    $18(a0),d0    ;SERDATR
  445. ;Then clear the RBF-bit of INTREQ
  446.         move.w    #$0800,$9c(a0)
  447.         tst.b    d0
  448.         bpl.s    RBF_nostatus
  449. ;If it's greater than $F7, it's a single-byte realtime message.
  450. ;We just ignore it.
  451.         cmp.b    #$f7,d0
  452.         bhi.s    RBF_exit
  453. ;The status (command) byte is saved to the first of the 3 bytes.
  454.         move.b    d0,(a1)
  455. ;The read counter is then reset.
  456.         clr.b    3(a1)
  457. RBF_exit    rts
  458. RBF_nostatus    moveq    #0,d1
  459. ;Get the read counter..
  460.         move.b    3(a1),d1
  461. ;Save the byte..
  462.         move.b    d0,1(a1,d1.w)
  463. ;Increment counter...
  464.         addq.b    #1,d1
  465. ;If it's 2 or greater, we signal the main task
  466.         cmp.b    #2,d1
  467.         bge.s    RBF_signal
  468. ;If not, we just save the counter, and exit.
  469.         move.b    d1,3(a1)
  470.         rts
  471. ;First we reset the read counter so that the next byte will be
  472. ;written into the second byte (for handling the running status)
  473. RBF_signal    clr.b    3(a1)
  474. ;Now get the status byte (command).
  475.         move.b    (a1),d0
  476. ;Mask out the channel.
  477.         and.b    #$f0,d0
  478. ;Test if it's Note On (this could be easily extended to handle
  479. ;other messages e.g. controller, pitchbender...)
  480.         cmp.b    #$90,d0
  481.         beq.s    RBF_sig
  482. ;Try if it's Note Off.
  483.         cmp.b    #$80,d0
  484. ;If it isn't we just exit.
  485.         bne.s    RBF_nosig
  486. ;Copy the note data to the input note buffer of the task.
  487. RBF_sig        lea    _inputbuff,a0
  488.         move.b    (a1)+,(a0)+
  489.         move.b    (a1)+,(a0)+
  490.         move.b    (a1),(a0)
  491. ;And send it a signal.
  492.         movea.l    _maintsk,a1
  493.         move.l    _sigmask,d0
  494.         jsr    -$144(a6)    ;Signal()
  495. RBF_nosig    rts
  496.  
  497. ;A temporary three-byte buffer. The fourth byte is the number of the
  498. ;byte most recently written.
  499. recmidi:    dc.b    0,0,0,0
  500.  
  501. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  502.  
  503. These were the main MIDI routines. We still need a routine to free the
  504. allocated resources.
  505.  
  506. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  507. _FreeSerial    move.l    a6,-(sp)
  508. ;First let's make a check if the serial port is allocated. This way the
  509. ;application programmer can call this routine on exit, even if GetSerial
  510. ;had failed.
  511.         tst.b    serportalloc
  512.         beq.s    FreeSer_xit
  513. ;First we disable the serial interrupts, so that they won't occur
  514. ;after resetting the interrupts.
  515.         move.w    #$0801,$dff09a    ;disables both RBF and TBE
  516.         movea.l    4,a6
  517. ;Now resetting the TBE interrupt.
  518.         moveq    #0,d0
  519.         move.l    prevtbe(pc),a1
  520.         jsr    -$a2(a6)    ;SetIntVector()
  521. ***** This part should be ignored if no RBF interrupt is installed *********
  522.         moveq    #11,d0
  523.         move.l    prevrbf(pc),a1
  524.         jsr    -$a2(a6)    ;SetIntVector()
  525. ****************************************************************************
  526. ;Then free the serial port using routine FreeMiscResource() (offset -$C)
  527.         movea.l    miscresbase(pc),a6
  528.         moveq    #0,d0
  529.         jsr    -$c(a6)        ;FreeMiscResource()
  530.         clr.b    serportalloc
  531. FreeSer_xit    move.l    (sp)+,a6
  532.         rts
  533. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  534.  
  535. Still there's one routine that may be useful. There may be need to reset
  536. the above routines by flushing the buffers and forgetting the previous
  537. status byte. Here's it:
  538.  
  539. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  540.  
  541. _ResetMIDI:    move.l    a6,-(sp)
  542. ;We keep interrupts disabled, the interrupts might otherwise touch
  543. ;the data we're resetting.
  544.         movea.l    4,a6
  545.         move.w    #$4000,$dff09a
  546.         addq.b    #1,$126(a6)
  547. ;a1 = pointer to buffptr, for address register relative addressing
  548.         lea    buffptr(pc),a1
  549.         lea    sendbuffer(pc),a0
  550. ;reset both the buffer read and write pointers
  551.         move.l    a0,(a1)+
  552.         move.l    a0,(a1)+
  553. ;clear the 'bytesinbuff' counter
  554.         clr.b    (a1)+
  555. ;skip the 'bufferempty' (it is reset by the TBE interrupt when it's
  556. ;finished its job)
  557.         addq.l    #1,a1
  558. ;clear the 'lastcmdbyte', to reset the running status byte system
  559.         clr.b    (a1)
  560.         subq.b    #1,$126(a6)
  561.         bge.s    ResetM_x
  562.         move.w    #$c000,$dff09a
  563. ResetM_x    move.l    (sp)+,a6
  564.         rts
  565. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  566. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  567.  
  568. I've collected all these routines into one file: midiroutines.a
  569. You can insert the routines directly into your programs (remove XDEFs
  570. and XREFs after doing that) or if your program consists of several object
  571. files, you can link the object code using Blink.
  572.  
  573. There's a single flag, INPUT, which is used to include/exclude the portions
  574. of code that handle MIDI input. Set it to 1 if you want to have the input
  575. routines.
  576.  
  577. For those who want to use the routines in C-programs (SAS/Lattice C V5),
  578. I've provided 'midiroutines.h', a header file that defines the prototypes
  579. for the functions.
  580.  
  581. I've also provided the object files (so that C-programmers don't require
  582. to use an assembler):
  583.     midiroutines.o        INPUT set to 0
  584.     midiroutines_i.o    INPUT set to 1
  585.  
  586. Also, note that _InitSerial call is not required during startup (in fact,
  587. that function even hasn't been XDEF'd). The 'midiroutines.a' automatically
  588. performs it during serial port allocation.
  589.  
  590. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  591.  
  592. Finally, I've written two programs to demonstrate how to use these routines.
  593.  
  594. The first one is called 'example1', a C-program which, respectively,
  595. plays the C-major chord. It demonstrates only how to output data.
  596.  
  597. The second example is more interesting. It demonstrates both the output and
  598. input procedures. When you run it, it waits until you press a key on your
  599. MIDI keyboard (supposing you've connected it to the MIDI IN of the Amiga).
  600. Then it displays the name of the note you pressed, and sends it out
  601. immediately, transposed 12 halfsteps (one octave) up. Exit by pressing
  602. CTRL-C. This program is called 'transposedemo' and it's written in
  603. assembler.
  604.  
  605. Both the executables, sources, and the .lnk files are included. Have fun
  606. with them!
  607.  
  608. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  609.